#ifndef __M_CONNECTION_H__
#define __M_CONNECTION_H__

#pragma once

#include "log.hpp"
#include "buffer.hpp"
#include "any.hpp"
#include "socket.hpp"
#include "poller.hpp"
#include "channel.hpp"
#include "timerWheel.hpp"
#include "eventLoop.hpp"

//Connection模块是对通信连接所有操作的统一管理

enum ConnectionStatus
{
    DISCONNECTED,
    CONNECTING,
    CONNECTED,
    DISCONNECTING,//连接待关闭状态
};

class Connection;
using ConnectionPtr = std::shared_ptr<Connection>;
class Connection : public std::enable_shared_from_this<Connection>//通过这个继承 可以调用shared_from_this接口通过weak_ptr拷贝构造一个shared_ptr
{
private:
    using ConnectionCallback = std::function<void(const ConnectionPtr&)>;//消息处理回调(发送/接收数据)是否需要手动传入Buffer 还是使用bind进行绑定

    uint64_t _cid;//连接的唯一标识id
    //uint64_t _timer_task_id//定时任务id 直接套用连接id即可
    Socket _sock;//连接套接字
    EventLoop* _loop;//Poller事件监控 TimerWheel定时任务管理 内置任务池
    Channel _channel;//TODO 这里要不要直接用对象不用指针
    Buffer _inbuffer;//输入缓冲区
    Buffer _outbuffer;//输出缓冲区
    Any _context;//协议上下文结构
    ConnectionStatus _st;//连接状态
    bool _enable_inactive_release;//是否开启非活跃连接定时释放功能
    //阶段性回调 根据连接状态调用
    ConnectionCallback _connected_cb;
    ConnectionCallback _closed_cb;
    ConnectionCallback _message_cb;//业务处理回调？
    ConnectionCallback _events_cb;
    ConnectionCallback _srv_closed_cb;

private:
    void handleRead()//TODO: 读写失败后调用关闭连接的逻辑可能存在问题
    {
        char buf[65535] = {0};
        int n = _sock.Recv(buf, 65535, MSG_DONTWAIT);
        if (n < 0)
        {
            checkOutBufferInLoop();//读取数据失败 连接出现问题 需要进行关闭 在关闭前先检查输出缓冲区有无数据待发送 有则监控写事件 就绪后发送 无则直接关闭连接
            return;
        }
        //如果客户端已经关闭连接 Recv返回-1 如果没有读取到数据或读取时被信号打断 Recv返回0 走到这里n >= 0
        _inbuffer.Write(buf, n);//n-1去掉\n***
        if (_inbuffer.getAbleReadSize() > 0)//输入缓冲区有数据可以处理
            _message_cb(shared_from_this());
    }

    void handleWrite()
    {
        int n = _outbuffer.getAbleReadSize();
        char buf[n];
        _outbuffer.Read(buf, n);
        n = _sock.Send(buf, n, MSG_DONTWAIT);
        if (n < 0)
        {
            // if (_inbuffer.getAbleReadSize() > 0)//发送失败 但接收缓冲区还有数据待处理 先处理数据再关闭连接
            //     _message_cb(shared_from_this());
            // closeConnection();//发送数据失败 连接出现问题 由于发送缓冲区数据已经发送不了 所以直接对连接进行关闭
            if (_st == DISCONNECTING)//由于读取客户端数据失败后开启写事件监控 触发写事件后调用的handleWrite 则发送失败后直接关闭连接
                closeConnection();//将连接的关闭释放操作统一放到本轮事件处理完成后再进行释放(EventLoop::runAllTask释放)
            else
                handleClose();//如果仅由于发送数据失败要关闭连接 发送失败后需要检查输入缓冲区还有无数据待处理 再关闭连接
            //TODO 在关闭连接前需不需要取消channel的事件监控
            return;
        }
        //到这里 发送缓冲区数据已成功发送
        if (_outbuffer.getAbleReadSize() == 0)//发送缓冲区数据已全部读走并发送
        {
            _channel.cancelMonitorWrite();//当发送缓冲区已没有数据发送 需要取消写事件监控 否则会出现一直监控提醒但无数据发送的情况
            if (_st == DISCONNECTING)//若连接处于待关闭状态 说明是由于读取客户端数据失败或回显数据发送后调用的handleWrite 将输出缓冲区剩余数据发送完成后直接关闭连接 
                closeConnection();
        }
        //如果发送缓冲区仍存在数据 则继续对写事件进行监控
    }

    void handleClose()
    {
        if (_inbuffer.getAbleReadSize() > 0)
            _message_cb(shared_from_this());
        closeConnection();//连接超时关闭时 不要立即释放连接 而是等待整轮事件处理完毕后再关闭
    }

    void handleError()
    {
        handleClose();
    }

    void handleEvents()
    {
        if (_enable_inactive_release)//开启了非活跃连接销毁的定时任务
            _loop->refreshTimerTask(_cid);//重置定时任务时间
        if (_events_cb)
            _events_cb(shared_from_this());
    }

    void establishConnectionInLoop()
    {
        if (_st != CONNECTING)
        {
            LOG(ERR, "connection status error");
            exit(-1);
        }
        _st = CONNECTED;
        //添加描述符读事件监控
        _channel.monitorRead();
        //如果设置了连接建立后的回调 则执行
        if (_connected_cb)
            _connected_cb(shared_from_this());
    }

    /*
    连接超时释放时 如果定时器描述符触发读事件 超时释放一批连接后 这批连接也刚好有事件到来 在处理完定时器描述符之后就会紧接着对这批连接的事件进行处理
    但由于连接已被超时释放 所以处理这批连接时会出现内存访问错误导致服务器崩溃 因此我们需要等待一整轮事件处理完毕后再对超时连接进行释放
    那么这样一来 如果超时事件和就绪事件一前一后到来 超时释放连接不立即执行 那么就绪事件到来时会刷新定时任务 而此时虽然连接未被释放 但定时任务已经从hash中删除 通过id已经找不到任务
    所以定时不会刷新 等待一轮事件处理完毕后 连接一定会被释放

    处理超时事件后不立即释放连接 可能接着处理读事件 读事件处理完毕后可能会发送响应 开启写事件监控 等待下一轮写事件触发后发送
    但是在本轮事件处理完后 会对连接进行释放 移除事件监控关闭描述符 所以响应无法返回给客户端
    
    如果连接的读写事件先于定时器事件 那么会对定时任务进行刷新 在处理定时器事件时 定时任务已经更新了位置 本次处理定时器事件就不会释放该连接

    以上意外情况的处理只是为了防止定时器事件释放的超时连接中 刚好有后续就绪事件的连接 连接如果被立即释放 后续对该连接的事件处理出现内存访问错误导致服务器崩溃的情况
    而超时后如果有读事件同时到来 这次读事件也会是无效处理 因为本轮开启写事件监控之后 连接被释放掉并且移除事件监控 后续的事件监控便不会触发写事件
    */
    void closeConnectionInLoop()//发送接收数据失败时调用/定时关闭任务触发调用
    {
        _st = DISCONNECTED;
        //取消对描述符的监控 也就是将描述符彻底从epoll模型中删除 同时从poller的hash结构中删除
        _channel.removeEventMonitor();
        //关闭描述符
        _sock.Close();
        //如果时间轮中还存在关于该描述符连接的定时关闭任务 则取消该定时任务
        if (_loop->hasTimerTask(_cid))
            _loop->cancelTimerTask(_cid);
        //如果设置了连接关闭后的回调 则执行
        if (_closed_cb)
            _closed_cb(shared_from_this());
        //TODO 移除server模块管理的连接信息 (shared_ptr<Connection>的释放？)
        if (_srv_closed_cb)
            _srv_closed_cb(shared_from_this());
    }

    //这个接口不是实际的数据发送接口 只是将处理结果放入输入缓冲区 并开启写事件监控 实际的数据发送由handleWrite进行
    void sendInLoop(Buffer& buf)
    {
        if (_st == DISCONNECTED)
            return;
        _outbuffer.WriteBuffer(buf);
        if (!_channel.isMonitorWrite())
            _channel.monitorWrite();
    }

    void checkOutBufferInLoop()//在客户端断开连接前 先检查输出缓冲区有没有数据待发送/待接收 向客户端发送后再断开连接
    {//TODO: 要不要检查输入缓冲区有无数据处理   
        _st = DISCONNECTING;//将连接设置为半关闭状态 进入半关闭状态说明读取客户端数据失败 准备对连接进行关闭
        //在当前函数内 有三种关闭连接的情况 1.数据发送失败调用handleClose关闭连接 2.数据发送成功后关闭连接 3.发送缓冲区无数据待发送时关闭连接
        if (_outbuffer.getAbleReadSize() > 0)
        {
            if (!_channel.isMonitorWrite())
                _channel.monitorWrite();
        }
        else
        {
            closeConnection();
        }
    }

    void enableInactiveReleaseInLoop(int sec)//开启非活跃连接定时释放 并指定释放时间
    {
        _enable_inactive_release = true;
        if (!_loop->hasTimerTask(_cid))
            _loop->addTimerTask(_cid, sec, std::bind(&Connection::handleClose, this));//TODO handleClose要不要换成closeConnection
        else
            _loop->refreshTimerTask(_cid);
    }

    void cancelInactiveReleaseInLoop()
    {
        _enable_inactive_release = false;
        if (_loop->hasTimerTask(_cid))
            _loop->cancelTimerTask(_cid);
    }

    void upgradeInLoop(Any& context, 
                 ConnectionCallback& connected_cb,
                 ConnectionCallback& closed_cb,
                 ConnectionCallback& message_cb,
                 ConnectionCallback& events_cb)//协议切换 替换上下文结构和阶段性回调
    {
        _context = context;
        _connected_cb = connected_cb;
        _closed_cb = closed_cb;
        _message_cb = message_cb;
        _events_cb = events_cb;
    }

public:
    Connection(EventLoop* loop, uint64_t connection_id, int fd)
    : _cid(connection_id),
      _sock(fd),
      _loop(loop),
      _channel(fd, loop),
      _st(CONNECTING),
      _enable_inactive_release(false)
    {
        //设置连接的事件监控回调
        _channel.setReadCallback(std::bind(&Connection::handleRead, this));
        _channel.setWriteCallback(std::bind(&Connection::handleWrite, this));
        _channel.setCloseCallback(std::bind(&Connection::handleClose, this));
        _channel.setEventsCallback(std::bind(&Connection::handleEvents, this));
        _channel.setErrorCallback(std::bind(&Connection::handleError, this));
        /*
        不能在Connection的构造函数中开启读事件监控 因为如果立即触发错误事件需要执行关闭回调
        那么Connection对象构造完成后会立即执行handleClose释放连接
        但此时设置连接的阶段性回调,将连接放入server模块管理的hash以及开启读事件监控这些操作都还未进行
        就会出现一系列问题 比如连接已经释放 并且从poller管理的channels中移除 这时再来开启读事件监控就会出大问题
        所以读事件监控需要在Connection对象的一系列初始化操作完成之后开启
        */
    }

    ~Connection()
    { LOG(DEG, "Connection closed, id: %d", _cid); }

    uint64_t getConnectionId() { return _cid; }

    int getFd() { return _sock.getFd(); }

    Buffer* getInBuffer() { return &_inbuffer; }

    void setContext(const Any& context) { _context = context; }

    Any* getContext() { return &_context; }//返回成员指针 以便直接对上下文内容进行修改

    ConnectionStatus getStatus() { return _st; }

    //设置连接的阶段性处理回调
    void setConnectedCallback(const ConnectionCallback& connected_cb) { _connected_cb = connected_cb; }

    void setClosedCallback(const ConnectionCallback& closed_cb) { _closed_cb = closed_cb; }

    void setMessageCallback(const ConnectionCallback& message_cb) { _message_cb = message_cb; }

    void setEventsCallback(const ConnectionCallback& events_cb) { _events_cb = events_cb; }

    void setSrvClosedCallback(const ConnectionCallback& srv_closed_cb) { _srv_closed_cb = srv_closed_cb; }

    void establishConnection() { _loop->runInLoop(std::bind(&Connection::establishConnectionInLoop, this)); }

    void closeConnection() { _loop->pushTaskInLoop(std::bind(&Connection::closeConnectionInLoop, this)); }

    //发送任务可能被压入loop任务池中 没有被立即执行 一旦data的源空间释放 就会造成野指针问题 所以在放入loop执行时不能直接传入data
    void send(const char* data, uint64_t size)
    { 
        Buffer buf;
        buf.Write(data, size);
        _loop->runInLoop(std::bind(&Connection::sendInLoop, this, std::move(buf)));
    }

    void checkOutBuffer() { _loop->runInLoop(std::bind(&Connection::checkOutBufferInLoop, this)); }
    
    void enableInactiveRelease(int sec) { _loop->runInLoop(std::bind(&Connection::enableInactiveReleaseInLoop, this, sec)); }

    void cancelInactiveRelease() { _loop->runInLoop(std::bind(&Connection::cancelInactiveReleaseInLoop, this)); } 

    //协议切换是非线程安全的 如果切换协议被压入任务池没有被立即执行 后续有新事件到来时会以旧协议的规则进行处理 因此必须保证loop在自身对应的线程中调用该接口
    void upgrade(Any& context, 
                 ConnectionCallback& connected_cb,
                 ConnectionCallback& closed_cb,
                 ConnectionCallback& message_cb,
                 ConnectionCallback& events_cb)
    {
        _loop->assertInLoop();
        _loop->runInLoop(std::bind(&Connection::upgradeInLoop, this, context, connected_cb, closed_cb, message_cb, events_cb));
    }
};


#endif
